<!--
 * Copyright 2011, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Google I/O 2011 - lots of objects ($)</title>
<link rel="stylesheet" href="style.css" />
<script src="colors.js"></script>
<script src="../../tdl/base.js"></script>
<script>
tdl.require('tdl.buffers');
tdl.require('tdl.fast');
tdl.require('tdl.fps');
tdl.require('tdl.log');
tdl.require('tdl.math');
tdl.require('tdl.models');
tdl.require('tdl.primitives');
tdl.require('tdl.programs');
tdl.require('tdl.textures');
tdl.require('tdl.webgl');
window.onload = initialize;

var g = {
  img: "assets/google-color.png",
};

// globals
var gl;                   // the gl context.
var canvas;               // the canvas
var math;                 // the math lib.
var fast;                 // the fast math lib.

var g_eyeSpeed          = 0.5;
var g_eyeHeight         = 2;
var g_eyeRadius         = 30;

function CreateApp(img) {

  // Create Geometry.
  var scale = 0.2;
  var sphereArrays = tdl.primitives.createSphere(0.4 * scale, 3, 3);
  var cubeArrays = tdl.primitives.createCube(0.8 * scale);
  var coneArrays = tdl.primitives.createTruncatedCone(
      0.4 * scale, 0.0 * scale, 0.8 * scale, 3, 1, true, true);

  // Load textures
  var textures = {
      diffuseSampler: tdl.textures.loadTexture('assets/google.png')
  };

  // Create Shader Program
  var program = tdl.programs.loadProgramFromScriptTags(
      'sphereVertexShader',
      'sphereFragmentShader');

  // --Setup Arrays---------------------------------------
  var arrays = [
    sphereArrays,
    cubeArrays,
    coneArrays
  ];

  // -- Setup Instances ----------------------------------
  var instances = [];
  var c = document.createElement("canvas");
  c.width = img.width;
  c.height = img.height;
  var ctx = c.getContext("2d");
  ctx.drawImage(img, 0, 0);
  var pixels = ctx.getImageData(0, 0, c.width, c.height);
  for (var yy = 0; yy < c.height; ++yy) {
    for (var xx = 0; xx < c.width; ++xx) {
      var offset = (yy * c.width + xx) * 4;
      var r = pixels.data[offset + 0];
      var g = pixels.data[offset + 1];
      var b = pixels.data[offset + 2];
      var a = pixels.data[offset + 3];
      if (r > 125 || g > 125 || b > 125) {
        instances.push({
          x: 0,
          y: 0,
          z: 0,
          x2: (xx - c.width / 2) * 0.1,
          y2: (yy - c.height / 2) * -0.1,
          z2: 0,
          colorMult: new Float32Array([r / 255, g / 255, b / 255, 1]),
          arrayIndex: Math.floor(Math.random() * arrays.length),
          xRadius: Math.random() * 25,
          yRadius: Math.random() * 25,
          zRadius: Math.random() * 25,
          xClockSpeed: (Math.random() + 0.5),
          yClockSpeed: (Math.random() + 0.5),
          zClockSpeed: (Math.random() + 0.5),
          xClock: Math.random() * Math.PI * 2,
          yClock: Math.random() * Math.PI * 2,
          zClock: Math.random() * Math.PI * 2
        });
      }
    }
  }

  document.title = document.title.replace("$", instances.length);
  document.getElementById("numObjects").innerHTML = instances.length;

  // Expand arrays from instances to geometry.

  // Step 1: Add an extra colorMult, clockOffset, clockSpeed,
  //     and radius fields to each geometry
  for (var ii = 0; ii < arrays.length; ++ii) {
    var numElements = arrays[ii].position.numElements;
    arrays[ii].colorMult = new tdl.primitives.AttribBuffer(4, numElements);
    arrays[ii].clockOffset = new tdl.primitives.AttribBuffer(3, numElements);
    arrays[ii].clockSpeed = new tdl.primitives.AttribBuffer(3, numElements);
    arrays[ii].radius = new tdl.primitives.AttribBuffer(3, numElements);
    arrays[ii].position2 = new tdl.primitives.AttribBuffer(3, numElements);
  }

  // Step 2: convert instances to expanded geometry
  var expandedArrays = [];
  var expandedArray;
  var totalElements = 0;
  for (var ii = 0; ii < instances.length; ++ii) {
    var instance = instances[ii];
    // WebGL can only handle 65536 indexed vertices so check if this
    // geometry can fit the current model
    var array = arrays[instance.arrayIndex];
    if (!expandedArray || totalElements + array.position.numElements > 65536) {
      // Start a new array.
      totalElements = 0;
      expandedArray = [array];
      expandedArrays.push(expandedArray);
    } else {
      // Add our new stuff on to the old one.
      expandedArray.push(array);
    }
    instance.firstVertex = totalElements;
    totalElements += array.position.numElements;
    instance.numVertices = array.position.numElements;
    instance.expandedArrayIndex = expandedArrays.length - 1;
  }

  for (var ii = 0; ii < expandedArrays.length; ++ii) {
    tdl.log("concat:", ii, " of ", expandedArrays.length);
    expandedArrays[ii] = tdl.primitives.concat(expandedArrays[ii]);
  }

  // Step 3: Make models from our expanded geometry.
  var models = [];
  for (var ii = 0; ii < expandedArrays.length; ++ii) {
    models.push(new tdl.models.Model(program, expandedArrays[ii], textures));
  }

  // Step 4: Copy in Colors and other per instance data.
  for (var ii = 0; ii < instances.length; ++ii) {
    var instance = instances[ii];
    var index = instance.expandedArrayIndex;
    expandedArrays[index].colorMult.fillRange(
        instance.firstVertex, instance.numVertices, instance.colorMult);
    expandedArrays[index].clockOffset.fillRange(
        instance.firstVertex, instance.numVertices,
        [instance.xClock, instance.yClock, instance.zClock]);
    expandedArrays[index].clockSpeed.fillRange(
        instance.firstVertex, instance.numVertices,
        [instance.xClockSpeed, instance.yClockSpeed, instance.zClockSpeed]);
    expandedArrays[index].radius.fillRange(
        instance.firstVertex, instance.numVertices,
        [instance.xRadius, instance.yRadius, instance.zRadius]);
    expandedArrays[index]. position2.fillRange(
        instance.firstVertex, instance.numVertices,
        [instance.x2, instance.y2, instance.z2]);
  }
  for (var ii = 0; ii < models.length; ++ii) {
    models[ii].setBuffer('colorMult', expandedArrays[ii].colorMult);
    models[ii].setBuffer('clockOffset', expandedArrays[ii].clockOffset);
    models[ii].setBuffer('clockSpeed', expandedArrays[ii].clockSpeed);
    models[ii].setBuffer('radius', expandedArrays[ii].radius);
    models[ii].setBuffer('position2', expandedArrays[ii].position2);
  }

  // pre-allocate a bunch of arrays
  var projection = new Float32Array(16);
  var view = new Float32Array(16);
  var world = new Float32Array(16);
  var worldInverse = new Float32Array(16);
  var worldInverseTranspose = new Float32Array(16);
  var viewProjection = new Float32Array(16);
  var worldViewProjection = new Float32Array(16);
  var viewInverse = new Float32Array(16);
  var viewProjectionInverse = new Float32Array(16);
  var eyePosition = new Float32Array(3);
  var target = new Float32Array(3);
  var up = new Float32Array([0,1,0]);
  var lightWorldPos = new Float32Array(3);
  var v3t0 = new Float32Array(3);
  var v3t1 = new Float32Array(3);
  var v3t2 = new Float32Array(3);
  var v3t3 = new Float32Array(3);
  var m4t0 = new Float32Array(16);
  var m4t1 = new Float32Array(16);
  var m4t2 = new Float32Array(16);
  var m4t3 = new Float32Array(16);
  var zero4 = new Float32Array(4);
  var one4 = new Float32Array([1,1,1,1]);

  // uniforms.
  var sharedUniforms = {
    viewInverse: viewInverse,
    lightWorldPos: lightWorldPos,
    specular: one4,
    shininess: 50,
    specularFactor: 0.2,
    viewProjection: viewProjection,
  };

  var clock = 0.0;
  function update(elapsedTime) {
    clock += elapsedTime;

    // Make the camera rotate around the scene.
    eyePosition[0] = Math.sin(clock * g_eyeSpeed) * g_eyeRadius;
    eyePosition[1] = g_eyeHeight;
    eyePosition[2] = Math.cos(clock * g_eyeSpeed) * g_eyeRadius;
  }

  function render() {
    renderBegin();
    renderScene();
    renderEnd();
  }

  function renderBegin() {
    var m4 = fast.matrix4;
    // clear the screen.
    gl.colorMask(true, true, true, true);
    gl.depthMask(true);
    gl.clearColor(1,1,1,0);
    gl.clearDepth(1);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

    gl.enable(gl.CULL_FACE);
    gl.enable(gl.DEPTH_TEST);

    // Compute a projection and view matrices.
    m4.perspective(
        projection,
        math.degToRad(60),
        canvas.clientWidth / canvas.clientHeight,
        1,
        5000);
    m4.lookAt(
        view,
        eyePosition,
        target,
        up);
    m4.mul(viewProjection, view, projection);
    m4.inverse(viewInverse, view);
    m4.inverse(viewProjectionInverse, viewProjection);

    // Put the light near the camera
    m4.getAxis(v3t0, viewInverse, 0); // x
    m4.getAxis(v3t1, viewInverse, 1); // y
    m4.getAxis(v3t2, viewInverse, 2); // z
    fast.mulScalarVector(v3t0, 10, v3t0);
    fast.mulScalarVector(v3t1, 10, v3t1);
    fast.mulScalarVector(v3t2, 10, v3t2);
    fast.addVector(lightWorldPos, eyePosition, v3t0);
    fast.addVector(lightWorldPos, lightWorldPos, v3t1);
    fast.addVector(lightWorldPos, lightWorldPos, v3t2);
  }

  function renderScene() {
    // -- Render Models ---------------------------------------
    // We only need to render each model once
    // as they contain all the instances.
    sharedUniforms.clock = clock / 2;
    for (var ii = 0; ii < models.length; ++ii) {
      var model = models[ii];
      model.drawPrep(sharedUniforms);
      model.draw();
    }
  }

  function renderEnd() {
    // Set the alpha to 255.
    gl.colorMask(false, false, false, true);
    gl.clearColor(0,0,0,1);
    gl.clear(gl.COLOR_BUFFER_BIT);
  }

  return {
    update: update,
    render: render
  };
}

function initialize() {
  math = tdl.math;
  fast = tdl.fast;
  canvas = document.getElementById("canvas");
  var fpsTimer = new tdl.fps.FPSTimer();
  var fpsElem = document.getElementById("fps");

  gl = tdl.webgl.setupWebGL(canvas);
  if (!gl) {
    return false;
  }

  tdl.misc.applyUrlSettings(g);

  var img = new Image();
  img.src = g.img;
  img.onload = function() {
    init();
  }

  function init() {
    var app = CreateApp(img);
    var then = (new Date()).getTime() * 0.001;

    function render() {
      requestAnimationFrame(render);

      // Compute the elapsed time since the last rendered frame
      // in seconds.
      var now = (new Date()).getTime() * 0.001;
      var elapsedTime = now - then;
      then = now;

      // Update the FPS timer.
      fpsTimer.update(elapsedTime);
      fpsElem.innerHTML = fpsTimer.averageFPS;

      app.update(elapsedTime);
      app.render();
    }
    render();
  }
  return true;
}
</script>
</head>
<body>
<div id="info"><span><a href="http://threedlibrary.googlecode.com" target="_blank">tdl.js</a> - google i/o <code id="numObjects"></code> objects</span></div>
<div id="fpsContainer">
  <div class="fps">fps: <span id="fps"></div>
</div>
<div id="viewContainer">
<canvas id="canvas" width="1024" height="1024" style="width: 100%; height: 100%;"></canvas>
</div>
</body>
<script id="sphereVertexShader" type="text/something-not-javascript">
uniform mat4 viewProjection;
uniform vec3 lightWorldPos;
uniform mat4 viewInverse;
uniform float clock;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
attribute vec4 colorMult;
attribute vec3 clockOffset;
attribute vec3 clockSpeed;
attribute vec3 radius;
attribute vec3 position2;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
varying vec4 v_colorMult;
void main() {
  vec3 clocks = clockOffset + (clockSpeed * clock);
  vec4 pos = vec4(
      sin(clocks.x) * radius.x,
      sin(clocks.y) * radius.y,
      cos(clocks.z) * radius.z,
      1
    );
  pos = mix(pos, vec4(position2, 1), sin(clock * 0.7) * 0.5 + 0.5);
  mat4 world = mat4(
      vec4(1,0,0,0),
      vec4(0,1,0,0),
      vec4(0,0,1,0),
      pos
    );
  v_texCoord = texCoord;
  v_position = (viewProjection * world * position);
  v_normal = (world * vec4(normal, 0)).xyz;
  v_colorMult = colorMult;
  v_surfaceToLight = lightWorldPos - (world * position).xyz;
  v_surfaceToView = (viewInverse[3] - (world * position)).xyz;
  gl_Position = v_position;
}

</script>
<script id="sphereFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif

varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
varying vec4 v_colorMult;

uniform sampler2D diffuseSampler;
uniform vec4 specular;
uniform sampler2D bumpSampler;
uniform float shininess;
uniform float specularFactor;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}
void main() {
  vec4 diffuse = texture2D(diffuseSampler, v_texCoord) * v_colorMult;
  vec3 normal = normalize(v_normal);
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  vec4 litR = lit(dot(normal, surfaceToLight),
                    dot(normal, halfVector), shininess);
  gl_FragColor = vec4((
  vec4(1,1,1,1) * (diffuse * litR.y
                        + specular * litR.z * specularFactor)).rgb,
      diffuse.a);
}
</script>
</html>




